package com.easyapi.server.service.api.impl;

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.easyapi.common.base.UserTokenThreadHolder;
import com.easyapi.common.enums.StatusCode;
import com.easyapi.common.enums.datasource.DataSourceStatus;
import com.easyapi.common.enums.datasource.DataSourceType;
import com.easyapi.common.enums.datasource.DatasourceOperationEnum;
import com.easyapi.common.enums.datasource.SqlDriverClassName;
import com.easyapi.common.exception.BizException;
import com.easyapi.common.param.datasource.AddDatasourceParam;
import com.easyapi.common.param.datasource.DataSourcePageParam;
import com.easyapi.common.utils.BaseResponse;
import com.easyapi.common.utils.RegularUtil;
import com.easyapi.common.utils.ResultUtils;
import com.easyapi.common.vo.datasource.DataSourceListVo;
import com.easyapi.common.vo.datasource.DataSourcePageVo;
import com.easyapi.common.vo.metadata.TableStruct;
import com.easyapi.common.vo.user.ApiCenterUserVo;
import com.easyapi.dao.entity.api.ApiCenterDatasource;
import com.easyapi.dao.entity.api.ApiCenterProject;
import com.easyapi.dao.mapper.api.ApiCenterDatasourceMapper;
import com.easyapi.dao.mapper.api.ApiCenterProjectMapper;
import com.easyapi.server.config.PoolConfig;
import com.easyapi.server.database.mysql.MySQLMetadata;
import com.easyapi.server.observation.*;
import com.easyapi.server.service.api.DataSourceService;
import com.easyapi.server.service.api.ProjectService;
import com.easyapi.server.utils.CaffeineCacheUtil;
import com.easyapi.server.utils.DruidDataSourceUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.easyapi.server.utils.DruidDataSourceUtil.testDatasourceConnection;

/**
 * @author javadadi
 */
@Service
@Slf4j
public class DataSourceServiceImpl extends ServiceImpl<ApiCenterDatasourceMapper, ApiCenterDatasource> implements DataSourceService {


    @Resource
    private ApiCenterDatasourceMapper datasourceMapper;

    @Resource
    private ApiCenterProjectMapper projectMapper;

    @Resource
    private DatasourceObserver datasourceObserver;

    @Resource
    private DatasourceBeObserver datasourceBeObserver ;


    /**
     * 保存数据源
     *
     * @param param 保存数据源参数
     *              <p>id 更新使用</p>
     *              <p>dataName 数据源名称</p>
     *              <p>dataDesc 数据源描述</p>
     *              <p>datasourceUsername 数据源用户名</p>
     *              <p>datasourcePassword 数据源密码</p>
     *              <p>dataJson 数据源链接json</p>
     *              <p>type 数据源类型</p>
     * @return true
     */
    @Override
    public boolean saveDataSource(AddDatasourceParam param) {
        boolean update = Objects.nonNull(param.getId());
        Long id = param.getId();
        String dataName = param.getDataName();
        String dataDesc = param.getDataDesc();
        String dataJson = param.getDataJson();
        String username = param.getDatasourceUsername();
        String password = param.getDatasourcePassword();
        Integer type = param.getType();
        // 校验空参数
        if (StringUtils.isAnyBlank(dataName, dataDesc, dataJson, username, password)) {
            throw new BizException(StatusCode.PARAMS_ERROR);
        }
        // 校验长度
        if (RegularUtil.StrsLengthError(1, 255, dataDesc, dataJson, dataName, username, password)) {
            throw new BizException(StatusCode.PARAMS_ERROR);
        }
        ApiCenterUserVo currentUser = UserTokenThreadHolder.getCurrentUser();
        //数据源信息保存到数据库
        ApiCenterDatasource datasource = null;
        //更新操作
        if (update) {
            datasource = this.getById(id);
            if (Objects.isNull(datasource)) {
                throw new BizException(StatusCode.NO_SUCH_DATASOURCE);
            }
            if (Objects.equals(datasource.getStatus(), DataSourceStatus.DISABLE.getCode())) {
                throw new BizException(StatusCode.DISABLE_DATASOURCE);
            }
            datasource.setDataName(dataName);
            datasource.setDataJson(dataJson);
            datasource.setDataDesc(dataDesc);
            datasource.setDatasourceUsername(username);
            datasource.setDatasourcePassword(password);
            //目前暂时只支持mysql
            datasource.setType(DataSourceType.MYSQL.getDataSourceType());
            datasource.setActive(DataSourceStatus.INIT.getCode());
            datasource.setLinkState(DataSourceStatus.LINKING.getCode());
            saveOrUpdate(datasource);
        } else {
            datasource = new ApiCenterDatasource();
            datasource.setDatasourceUsername(username);
            datasource.setDatasourcePassword(password);
            datasource.setDataName(dataName);
            datasource.setDataJson(dataJson);
            datasource.setDataDesc(dataDesc);
            datasource.setCreateBy(currentUser.getId());
            datasource.setUpdateBy(currentUser.getId());
            datasource.setType(DataSourceType.MYSQL.getDataSourceType());
            datasource.setActive(DataSourceStatus.INIT.getCode());
            datasource.setLinkState(DataSourceStatus.LINKING.getCode());
            save(datasource);
        }
        DruidDataSource druidDataSource = DruidDataSourceUtil.getDruidDataSource(dataJson, username, password, SqlDriverClassName.MYSQL.getDriverClassName());
        if (Objects.isNull(druidDataSource)) {
            //连接失败
            datasource.setLinkState(DataSourceStatus.LINK_FAIL.getCode());
        } else {
            ApiCenterDatasource finalDatasource = datasource;
            PoolConfig.RUN_SYNC_JOB_POOL.execute(()->{
                String action = update?DatasourceOperationEnum.UPDATE.getAction():DatasourceOperationEnum.ADD.getAction();
                datasourceBeObserver.notifyObservers(action, finalDatasource.getId());
            });
            //链接成功就放到本地缓存
            datasource.setLinkState(DataSourceStatus.LINK_SUCCESS.getCode());
            CaffeineCacheUtil.add(datasource.getDataName() + "_" + datasource.getId(), druidDataSource);
        }
        datasource.setStatus(DataSourceStatus.NORMAL.getCode());
        datasource.setActive(DataSourceStatus.NOT_USING.getCode());
        updateById(datasource);
        return true;
    }

    /**
     * 数据源分页
     *
     * @param param 分页参数
     *              <p>dataName 数据源名称</p>
     *              <p>dataDesc 数据源描述</p>
     *              <p>datasourceUsername 数据源用户名</p>
     *              <p>type 数据源类型</p>
     *              <p>active 数据源使用状态 0-未启用 1-使用中</p>
     *              <p>linkState 数据源链接状态 0-链接丢失 1-链接可用</p>
     *              <p>Status 数据源状态0--正常，1--禁用</p>
     * @return
     */
    @Override
    public Page<DataSourcePageVo> page(DataSourcePageParam param) {
        String dataName = param.getDataName();
        String dataDesc = param.getDataDesc();
        String datasourceUsername = param.getDatasourceUsername();
        Integer type = param.getType();
        Integer active = param.getActive();
        Integer linkState = param.getLinkState();
        Integer status = param.getStatus();
        Long size = param.getSize();
        Long current = param.getCurrent();
        Page<ApiCenterDatasource> page = new Page<>(current, size);
        LambdaQueryWrapper<ApiCenterDatasource> wrapper = new LambdaQueryWrapper<ApiCenterDatasource>()
                .like(StringUtils.isNotEmpty(dataName), ApiCenterDatasource::getDataName, dataName)
                .like(StringUtils.isNotEmpty(dataDesc), ApiCenterDatasource::getDataDesc, dataDesc)
                .like(StringUtils.isNotEmpty(datasourceUsername), ApiCenterDatasource::getDatasourceUsername, datasourceUsername)
                .eq(Objects.nonNull(type), ApiCenterDatasource::getType, type)
                .eq(Objects.nonNull(active), ApiCenterDatasource::getActive, active)
                .eq(Objects.nonNull(linkState), ApiCenterDatasource::getLinkState, linkState)
                .eq(Objects.nonNull(status), ApiCenterDatasource::getStatus, param.getStatus());
        Page<ApiCenterDatasource> apiCenterDatasourcePage = page(page, wrapper);

        List<DataSourcePageVo> voList = apiCenterDatasourcePage.getRecords().stream().map(datasource -> {
            DataSourcePageVo vo = DataSourcePageVo.builder()
                    .id(datasource.getId())
                    .dataName(datasource.getDataName())
                    .dataDesc(datasource.getDataDesc())
                    .active(datasource.getActive())
                    .datasourceUsername(datasource.getDatasourceUsername())
                    .dataJson(datasource.getDataJson())
                    .type(datasource.getType())
                    .linkState(datasource.getLinkState())
                    .createAt(datasource.getCreateAt())
                    .updateAt(datasource.getUpdateAt())
                    .status(datasource.getStatus())
                    .build();
            return vo;
        }).collect(Collectors.toList());
        Page<DataSourcePageVo> resultPage = new Page<>(apiCenterDatasourcePage.getCurrent(), apiCenterDatasourcePage.getSize(), apiCenterDatasourcePage.getTotal());
        resultPage.setRecords(voList);
        return resultPage;
    }

    /**
     * 删除数据源
     *
     * @param id 数据源id
     * @return true
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeDataSource(Long id) {
        ApiCenterDatasource datasource = this.getById(id);
        if (Objects.isNull(datasource)) {
            throw new BizException(StatusCode.NO_SUCH_DATASOURCE);
        }
        //数据源在使用中不能删除
        if (Objects.equals(datasource.getActive(), DataSourceStatus.USING.getCode())) {
            throw new BizException(StatusCode.DATASOURCE_IS_USING);
        }
        String dataName = datasource.getDataName();
        DruidDataSource center = (DruidDataSource) CaffeineCacheUtil.get(dataName + "_" + datasource.getId());
        if (Objects.nonNull(center)) {
            center.close();
            CaffeineCacheUtil.remove(dataName + "_" + datasource.getId());
        }
        this.removeById(id);
        PoolConfig.RUN_SYNC_JOB_POOL.execute(()->{
            datasourceBeObserver.notifyObservers(DatasourceOperationEnum.DELETE.getAction(), id);
        });
        return true;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean datasourceOperation(Long Id, String action) {
        ApiCenterDatasource datasource = getById(Id);
        if (Objects.isNull(datasource)) {
            throw new BizException(StatusCode.NO_SUCH_DATASOURCE);
        }
        ApiCenterUserVo currentUser = UserTokenThreadHolder.getCurrentUser();
        if (!Objects.equals(currentUser.getId(), datasource.getCreateBy())) {
            throw new BizException(StatusCode.NO_AUTH_ERROR);
        }
        if (action.equalsIgnoreCase(DatasourceOperationEnum.BIND_DATASOURCE.getAction())) {
            //正在引用的数据源不能禁用
            if (Objects.equals(datasource.getActive(), DataSourceStatus.USING.getCode())) {
                throw new BizException(StatusCode.DATASOURCE_IS_USING);
            }
            datasource.setStatus(DatasourceOperationEnum.BIND_DATASOURCE.getCode());
        }
        if (action.equalsIgnoreCase(DatasourceOperationEnum.LIFE_DATASOURCE.getAction())) {
            datasource.setStatus(DatasourceOperationEnum.LIFE_DATASOURCE.getCode());
        }
        updateById(datasource);
        return true;
    }

    //测试数据源的可链接性
    @Override
    public boolean testDataSourceConnection(AddDatasourceParam param) {
        boolean isUpdate = false;
        if (Objects.nonNull(param.getId())) {
            isUpdate = true;
        }
        ApiCenterDatasource datasource = null;
        if (isUpdate) {
            datasource = this.getById(param.getId());
            if (Objects.isNull(datasource)) {
                throw new BizException(StatusCode.NO_SUCH_DATASOURCE);
            }
            if (Objects.equals(datasource.getStatus(), DataSourceStatus.DISABLE.getCode())) {
                throw new BizException(StatusCode.DISABLE_DATASOURCE);
            }
            testDatasourceConnection(datasource.getDataJson(), datasource.getDatasourceUsername(), datasource.getDatasourcePassword());
        } else {
            testDatasourceConnection(param.getDataJson(), param.getDatasourceUsername(), param.getDatasourcePassword());
        }
        return true;
    }

    @Override
    public List<String> listAllDatabases(Long id, String databaseName) {
        ApiCenterDatasource datasource = this.getById(id);
        if (Objects.isNull(datasource)) {
            throw new BizException(StatusCode.NO_SUCH_DATASOURCE);
        }
        String dataName = datasource.getDataName();
        DruidDataSource druidDataSource = (DruidDataSource) CaffeineCacheUtil.get(dataName + "_" + datasource.getId());
        if (Objects.isNull(druidDataSource)) {
            throw new BizException(StatusCode.USELESS_DATASOURCE);
        }
        List<String> strings = MySQLMetadata.listAllDatabases(druidDataSource);
        if (StringUtils.isNotEmpty(databaseName)) {
            strings = strings.stream().filter(s -> s.contains(databaseName)).collect(Collectors.toList());
        }
        return strings;
    }

    @Override
    public List<String> listAllTables(Long id, String databaseName, String tableName) {
        ApiCenterDatasource datasource = this.getById(id);
        if (Objects.isNull(datasource)) {
            throw new BizException(StatusCode.NO_SUCH_DATASOURCE);
        }
        String dataName = datasource.getDataName();
        DruidDataSource druidDataSource = (DruidDataSource) CaffeineCacheUtil.get(dataName + "_" + datasource.getId());
        if (Objects.isNull(druidDataSource)) {
            throw new BizException(StatusCode.USELESS_DATASOURCE);
        }
        List<String> strings = MySQLMetadata.listAllTables(druidDataSource, databaseName);
        if (StringUtils.isNotEmpty(tableName)) {
            strings = strings.stream().filter(s -> s.contains(tableName)).collect(Collectors.toList());
        }
        return strings;
    }

    @Override
    public BaseResponse<List<TableStruct>> getTableStruct(Long id, String databaseName, String tableName) {
        ApiCenterDatasource datasource = this.getById(id);
        if (Objects.isNull(datasource)) {
            throw new BizException(StatusCode.NO_SUCH_DATASOURCE);
        }
        String dataName = datasource.getDataName();
        DruidDataSource druidDataSource = (DruidDataSource) CaffeineCacheUtil.get(dataName + "_" + datasource.getId());
        if (Objects.isNull(druidDataSource)) {
            throw new BizException(StatusCode.USELESS_DATASOURCE);
        }
        List<TableStruct> strings = MySQLMetadata.getTableStruct(druidDataSource, databaseName, tableName);
        return ResultUtils.success(strings);
    }

    @Override
    public String getCreateSql(Long id, String databaseName, String tableName) {
        ApiCenterDatasource datasource = this.getById(id);
        if (Objects.isNull(datasource)) {
            throw new BizException(StatusCode.NO_SUCH_DATASOURCE);
        }
        String dataName = datasource.getDataName();
        DruidDataSource druidDataSource = (DruidDataSource) CaffeineCacheUtil.get(dataName + "_" + datasource.getId());
        if (Objects.isNull(druidDataSource)) {
            throw new BizException(StatusCode.USELESS_DATASOURCE);
        }
        String sql = MySQLMetadata.getCreateTableSql(druidDataSource, databaseName, tableName);
        return sql;
    }

    @Override
    public List<List<String>> getTableData(Long id, String databaseName, String tableName) {
        ApiCenterDatasource datasource = this.getById(id);
        if (Objects.isNull(datasource))
            throw new BizException(StatusCode.NO_SUCH_DATASOURCE);
        String dataName = datasource.getDataName();
        DruidDataSource druidDataSource = (DruidDataSource) CaffeineCacheUtil.get(dataName + "_" + datasource.getId());
        if (Objects.isNull(druidDataSource))
            throw new BizException(StatusCode.USELESS_DATASOURCE);
        List<List<String>> strings = MySQLMetadata.getTableData(druidDataSource, databaseName, tableName);
        return strings;
    }

    @Override
    public Boolean initDruidDatasource() {
        PoolConfig.RUN_SYNC_JOB_POOL.submit(() -> {
            List<ApiCenterDatasource> datasourceList = datasourceMapper.listAllDatasource();
            for (ApiCenterDatasource apiCenterDatasource : datasourceList) {
                Long id = apiCenterDatasource.getId();
                String dataName = apiCenterDatasource.getDataName();
                String dataDesc = apiCenterDatasource.getDataDesc();
                String datasourceUsername = apiCenterDatasource.getDatasourceUsername();
                String datasourcePassword = apiCenterDatasource.getDatasourcePassword();
                String dataJson = apiCenterDatasource.getDataJson();
                DruidDataSource druidDataSource = DruidDataSourceUtil.getDruidDataSource(dataJson, datasourceUsername, datasourcePassword, SqlDriverClassName.MYSQL.getDriverClassName());
                if (Objects.isNull(druidDataSource)) {
                    //初始化失败就更新一下链接状态
                    apiCenterDatasource.setLinkState(DataSourceStatus.LINK_FAIL.getCode());
                    log.error("datasource init error ，datasourceId:{},datasourceName:{},datasourceDesc:{}", id, dataName, dataDesc);
                    continue;
                }
                CaffeineCacheUtil.add(dataName + "_" + id, druidDataSource);
            }
            if (CollectionUtils.isNotEmpty(datasourceList)) {
                this.saveOrUpdateBatch(datasourceList);
            }
        });
        return true;
    }

    @Override
    public List<DataSourceListVo> listDataSource() {
        //数据源只能够获取当前用户自己导入的数据源
        List<ApiCenterDatasource> datasourceList = datasourceMapper.listAllDatasource();
        List<DataSourceListVo> dataSourceListVos = datasourceList.stream().map(item -> {
            DataSourceListVo build = DataSourceListVo.builder()
                    .id(item.getId())
                    .dataName(item.getDataName())
                    .dataDesc(item.getDataDesc())
                    .build();
            return build;
        }).collect(Collectors.toList());
        return dataSourceListVos;
    }

    @Override
    public List<DataSourceListVo> listDatasourceByProjectId(Long projectId) {
        ApiCenterProject project = projectMapper.selectById(projectId);
        if (Objects.isNull(project)){
            throw new BizException(StatusCode.NO_SUCH_PROJECT);
        }
        List<ApiCenterDatasource> datasourceList = datasourceMapper.listDatasourceByUserId(project.getId());
        List<DataSourceListVo> dataSourceListVos = datasourceList.stream().map(item -> {
            DataSourceListVo build = DataSourceListVo.builder()
                    .id(item.getId())
                    .dataName(item.getDataName())
                    .dataDesc(item.getDataDesc())
                    .build();
            return build;
        }).collect(Collectors.toList());
        return dataSourceListVos;
    }

}
